namespace Radar.Test
{
    using System;
    using System.Collections.Generic;
    using Moq;
    using NUnit.Framework;

    [TestFixture]
    public class ExpressionHelperTest
    {
        private interface IInterface
        {
        }

        private abstract class AbstractClass
        {
        }

        private class WithoutConstructor
        {
            private WithoutConstructor() { }
        }

        private class NoParameters
        {
        }

        private class WithParameter
        {
            public WithParameter(NoParameters x)
            {
                X = x;
            }

            public NoParameters X { get; private set; }
        }

        private class Generic<T>
        {
            public Generic(T x) { }
        }

        private Mock<IContainer> container;
        private IDictionary<string, Func<object>> parameters;
        private static NoParameters staticP = new NoParameters();

        private static object GetBuilder()
        {
            return staticP;
        }

        [SetUp]
        public void SetUp()
        {
            container = new Mock<IContainer>();
            parameters = new Dictionary<string, Func<object>>();
        }

        [Test]
        public void WillBuildWithParameterlessConstructor()
        {
            var builder = ExpressionHelper.BuildConstructor(typeof(NoParameters), parameters, container.Object);

            var result = builder(new ResolveContext());

            Assert.That(result, Is.InstanceOf<NoParameters>());
        }

        [Test]
        public void WillBuildWithParameters()
        {
            var builder = ExpressionHelper.BuildConstructor(typeof(WithParameter), parameters, container.Object);

            container.Verify(x => x.Resolve(typeof(NoParameters)), Times.Never());

            var context = new ResolveContext();
            var result = builder(context);

            container.Verify(x => x.Resolve<NoParameters>(context), Times.Once());

            Assert.That(result, Is.InstanceOf<WithParameter>());
        }

        [Test]
        public void WillBuildGenericTypeWithParameters()
        {
            var builder = ExpressionHelper.BuildConstructor(typeof(Generic<NoParameters>), parameters, container.Object);

            container.Verify(x => x.Resolve(typeof(NoParameters)), Times.Never());

            var context = new ResolveContext();
            var result = builder(context);

            container.Verify(x => x.Resolve<NoParameters>(context), Times.Once());

            Assert.That(result, Is.InstanceOf<Generic<NoParameters>>());
        }

        [Test]
        public void WillBuildWithParameterFromDictionary()
        {
            var p = new NoParameters();

            parameters["x"] = () => p;
            Assert.That(parameters["x"].Method.IsStatic, Is.False);
            Assert.That(parameters["x"].Target, Is.Not.Null);

            var builder = ExpressionHelper.BuildConstructor(typeof(WithParameter), parameters, container.Object);

            var result = builder(new ResolveContext());

            container.Verify(x => x.Resolve(typeof(NoParameters)), Times.Never());

            Assert.That(result, Is.InstanceOf<WithParameter>());
            Assert.That(((WithParameter)result).X, Is.EqualTo(p));
        }

        [Test]
        public void WillBuildWithParameterFromDictionaryWithStaticDelegate()
        {
            parameters["x"] = GetBuilder;
            Assert.That(parameters["x"].Method.IsStatic, Is.True);
            Assert.That(parameters["x"].Target, Is.Null);

            var builder = ExpressionHelper.BuildConstructor(typeof(WithParameter), parameters, container.Object);

            var result = builder(new ResolveContext());

            container.Verify(x => x.Resolve(typeof(NoParameters)), Times.Never());

            Assert.That(result, Is.InstanceOf<WithParameter>());
            Assert.That(((WithParameter)result).X, Is.EqualTo(staticP));
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void WillThrowOnNullTypeToBuild()
        {
            ExpressionHelper.BuildConstructor(null, parameters, container.Object);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void WillThrowOnNullParameterBuilderDictionary()
        {
            ExpressionHelper.BuildConstructor(typeof(object), null, container.Object);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void WillThrowOnNullContainer()
        {
            ExpressionHelper.BuildConstructor(typeof(object), parameters, null);
        }

        [Test, ExpectedException(typeof(InstanceCreationException))]
        public void WillThrowOnInterfaceType()
        {
            ExpressionHelper.BuildConstructor(typeof(IInterface), parameters, container.Object);
        }

        [Test, ExpectedException(typeof(InstanceCreationException))]
        public void WillThrowOnAbstractType()
        {
            ExpressionHelper.BuildConstructor(typeof(AbstractClass), parameters, container.Object);
        }

        [Test, ExpectedException(typeof(InstanceCreationException))]
        public void WillThrowOnTypeWithoutPublicConstructor()
        {
            ExpressionHelper.BuildConstructor(typeof(WithoutConstructor), parameters, container.Object);
        }

        [Test, ExpectedException(typeof(InstanceCreationException))]
        public void WillThrowOnGenericTypeDefinition()
        {
            ExpressionHelper.BuildConstructor(typeof(Generic<>), parameters, container.Object);
        }
    }
}